﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;

namespace SharedGenomics.Core
{
    public class KeyedIndexedCollection<TKey, TValue> : Collection<TValue>
    {
        private IDictionary<TKey, int> _itemIndexes;
        IEqualityComparer<TValue> _valueComparer;

        public KeyedIndexedCollection(Func<TValue, TKey> selector)
            : this(selector, null, null)
        {
        }

        public KeyedIndexedCollection(Func<TValue, TKey> selector, IEqualityComparer<TKey> keyComparer)
            : this(selector, keyComparer, null)
        {   
        }

        public KeyedIndexedCollection(Func<TValue, TKey> selector, IEqualityComparer<TValue> valueComparer)
            : this(selector, null, valueComparer)
        {
        }

        public KeyedIndexedCollection(Func<TValue, TKey> selector, IEqualityComparer<TKey> keyComparer, IEqualityComparer<TValue> valueComparer)
        {
            selector.ThrowIfNull("selector");

            this.SelectorFunc = selector;
            this._itemIndexes = new Dictionary<TKey, int>(keyComparer);
            this._valueComparer = valueComparer;
        }

        public void AddRange(IEnumerable<TValue> items)
        {
            if (items == null)
                return;

            foreach(TValue item in items)
            {
                this.Add(item);
            }
        }

        public int GetItemIndex(TValue item)
        {
            item.ThrowIfNull("item");
            TKey key = this.SelectorFunc(item);

            if (this._itemIndexes.ContainsKey(key))
            {
                int itemIndex = this._itemIndexes[key];
                TValue val = this[itemIndex];

                if(this.ValueComparer.Equals(item, val))
                    return itemIndex;
            }

            throw new ArgumentException("Item is not present in the collection");
        }

        public TValue this[TKey key]
        {
            get
            {
                int index = this._itemIndexes[key];
                return this[index];
            }
        }

        public bool ContainsKey(TKey key)
        {
            if ((object)key == null)
                return false;

            return this._itemIndexes.ContainsKey(key);
        }

        protected override void InsertItem(int index, TValue item)
        {
            TKey key = this.SelectorFunc(item);
            ExceptionHelper.ThrowIf<ArgumentException>(this._itemIndexes.ContainsKey(key), String.Format("An item with key {0} already exists in the collection", key));
            ExceptionHelper.ThrowIf<InvalidOperationException>(index < this.Count, "Items can only be added to the end of this collection");

            this._itemIndexes[key] = index;
            base.InsertItem(index, item);
        }

        protected override void SetItem(int index, TValue item)
        {
            //remove key/index for item currently at index
            TKey currentKey = this.SelectorFunc(this[index]);
            this._itemIndexes.Remove(currentKey);

            TKey newKey = this.SelectorFunc(item);
            this._itemIndexes[newKey] = index;
            base.SetItem(index, item);
        }

        protected override void RemoveItem(int index)
        {
            //TODO: implement if required...
            throw new InvalidOperationException("Cannot remove items from this collection");
            //base.RemoveItem(index);
        }

        protected override void ClearItems()
        {
            this._itemIndexes.Clear();
            base.ClearItems();
        }

        private Func<TValue, TKey> SelectorFunc { get; set; }

        private IEqualityComparer<TValue> ValueComparer
        {
            get { return this._valueComparer ?? EqualityComparer<TValue>.Default; }
        }
    }
}
